﻿using System.Diagnostics.Contracts;
using System.Linq;

namespace System
{
    ///<summary>Методы расширения для массивов</summary>
    public static class ArrayExtantions
    {
        ///<summary>Конвертация элементов массива</summary>
        ///<param name="In">Входной массив</param>
        ///<param name="converter">Метод конвертаци</param>
        ///<typeparam name="TIn">Тип элементов входного массива</typeparam>
        ///<typeparam name="TOut">Тип элементов выходного массива</typeparam>
        ///<returns>Массив элементов нового типа</returns>
        [Obsolete("Используйте LINQ.Select(...)", true)]
        [Diagnostics.DebuggerStepThrough]
        public static TOut[] ConvertAll<TIn, TOut>(this TIn[] In, Converter<TIn, TOut> converter)
        {
            Contract.Requires(In != null);
            Contract.Requires(converter != null);
            Contract.Ensures(Contract.Result<TOut[]>() != null);

            return Array.ConvertAll(In, converter);
        }

        /// <summary>Получить копию массива</summary>
        /// <typeparam name="TArray">Тип исходного массива</typeparam>
        /// <param name="Array">Исходный массив</param>
        /// <returns>Новый массив</returns>
        [Obsolete("Используйте (T[])array.Clone()", true)]
        [Diagnostics.DebuggerStepThrough]
        public static TArray[] GetCopy<TArray>(this TArray[] Array)
        {
            Contract.Requires(Array != null);
            Contract.Ensures(Contract.Result<TArray[]>() != null);
            Contract.Ensures(Contract.Result<TArray[]>().Length == Array.Length);

            return (TArray[])Array.Clone();
        }

        ///<summary>Объединение с массивом элементов</summary>
        ///<param name="A">Исходный массив</param>
        ///<param name="B">Присоединяемый массив</param>
        ///<typeparam name="TArray">Тип элементов массива</typeparam>
        ///<returns>Массив из объединёных элементов</returns>
        [Diagnostics.DebuggerStepThrough]
        public static TArray[] Concatinate<TArray>(this TArray[] A, TArray[] B)
        {
            Contract.Requires(A != null);
            Contract.Requires(B != null);
            Contract.Ensures(Contract.Result<TArray[]>() != null);
            Contract.Ensures(Contract.Result<TArray[]>().Length == A.Length + B.Length);

            var Result = new TArray[A.Length + B.Length];
            A.CopyTo(Result, 0);
            B.CopyTo(Result, A.Length);
            return Result;
        }

        //public delegate Complex BeamPattern(double Thetta, double Phi);

        ///<summary>Получить элемент массива</summary>
        ///<param name="A">Массив элементов</param>
        ///<param name="Selector">Метод выбора элемента массива</param>
        ///<typeparam name="TArray">Тип элементов массива</typeparam>
        ///<typeparam name="TOut">Тип выходного элемента</typeparam>
        ///<returns>Выбранный элемент массива</returns>
        [Diagnostics.DebuggerStepThrough]
        public static TOut GetSelectedValue<TArray, TOut>(this TArray[] A, Func<TArray, TOut, TOut> Selector)
        {
            Contract.Requires(Selector != null);
            Contract.Requires(A != null);

            var lv_Out = default(TOut);
            var lv_ILength = A.Length;
            for(var i = 0; i < lv_ILength; i++)
                lv_Out = Selector(A[i], lv_Out);
            return lv_Out;
        }

        ///<summary>Преобразовать тип элементов массива</summary>
        ///<param name="In">Исходный массив элементов</param>
        ///<param name="converter">Метод преобразования элемента массива</param>
        ///<typeparam name="TIn">Исходный тип элементов массива</typeparam>
        ///<typeparam name="TOut">Требуемый тип элементов массива</typeparam>
        ///<returns>Массив преобразованных элементов</returns>
        [Diagnostics.DebuggerStepThrough]
        public static TOut[] ConvertTo<TIn, TOut>(this TIn[] In, Converter<TIn, TOut> converter)
        {
            Contract.Requires(In != null);
            Contract.Requires(converter != null);
            Contract.Ensures(Contract.Result<TOut[]>() != null);

            var lv_Out = new TOut[In.Length];
            var i_Length = In.Length;
            for(var i = 0; i < i_Length; i++)
                lv_Out[i] = converter(In[i]);
            return lv_Out;
        }

        ///<summary>Выполнение действия для всех элементов массива</summary>
        ///<param name="array">Массив элементов</param>
        ///<param name="action">Выполняемой действие</param>
        ///<typeparam name="TArray">Тип элементов массива</typeparam>
        [Diagnostics.DebuggerStepThrough]
        public static void Foreach<TArray>(this TArray[] array, Action<TArray> action)
        {
            Contract.Requires(array != null);
            Contract.Requires(action != null);
            Array.ForEach(array, action);
        }

        ///<summary>Выполнение действия для всех элементов массива с обработкой исключений</summary>
        ///<param name="array">Массив элементов</param>
        ///<param name="action">Выполняемое действие</param>
        ///<param name="ErrorHandler">Обработчик исключения</param>
        ///<typeparam name="TArray">Тип элементов массива</typeparam>
        [Diagnostics.DebuggerStepThrough]
        public static void Foreach<TArray>(this TArray[] array, Action<TArray> action, Func<Exception, bool> ErrorHandler)
        {
            Contract.Requires(array != null);
            Contract.Requires(action != null);
            Contract.Requires(ErrorHandler != null);

            array.Foreach<TArray, Exception>(action, ErrorHandler);
        }

        ///<summary>Выполнение действия для всех элементов массива</summary>
        ///<param name="array">Массив элементов</param>
        ///<param name="action">ВЫполняемое действие</param>
        ///<param name="ErrorHandler">Обработчик исключений</param>
        ///<typeparam name="TArray">Тип элементов массива</typeparam>
        ///<typeparam name="TException">Тип исключений</typeparam>
        [Diagnostics.DebuggerStepThrough]
        public static void Foreach<TArray, TException>(this TArray[] array, Action<TArray> action,
            Func<TException, bool> ErrorHandler) where TException : Exception
        {
            Contract.Requires(array != null);
            Contract.Requires(action != null);
            Contract.Requires(ErrorHandler != null);

            var length = array.Length;
            for(var i = 0; i < length; i++)
                try
                {
                    action(array[i]);
                }
                catch(TException e)
                {
                    if(!ErrorHandler(e))
                        throw new Exception("Ошибка выполнения", e);
                }
        }

        ///<summary>Определение значения функции для всех элементов массива</summary>
        ///<param name="array">Массив элементов</param>
        ///<param name="f">Вычисляемая функция</param>
        ///<typeparam name="TIn">Тип элементов массива области определения</typeparam>
        ///<typeparam name="TOut">Тип элементов массива области значения</typeparam>
        ///<returns>Массив значений функции</returns>
        [Diagnostics.DebuggerStepThrough]
        public static TOut[] Function<TIn, TOut>(this TIn[] array, Func<TIn, TOut> f)
        {
            Contract.Requires(f != null);
            Contract.Requires(array != null);
            Contract.Ensures(Contract.Result<TOut[]>() != null);
            return array.Select(f).ToArray();
            //return array.ConvertAll(x => f(x));
        }

        [Diagnostics.DebuggerStepThrough]
        public static TArray[] GetReversed<TArray>(this TArray[] array)
        {
            Contract.Requires(array != null);
            Contract.Ensures(Contract.Result<TArray[]>() != null);

            (array = (TArray[])array.Clone()).Reverse();
            return array;
        }

        [Diagnostics.DebuggerStepThrough]
        public static TArray[] GetSubArray<TArray>(this TArray[] array, int Length, int Start = 0)
        {
            Contract.Requires(array != null);
            Contract.Requires(Length >= 0);
            Contract.Ensures(Contract.Result<TArray[]>() != null);
            Contract.Ensures(Contract.Result<TArray[]>() == array);

            var lv_Result = new TArray[Length];

            var j_Length = Start + Length;
            for(int i = 0, j = Start; i < Length && j < j_Length; i++, j++)
                lv_Result[i] = array[j];

            return lv_Result;
        }

        [Diagnostics.DebuggerStepThrough]
        public static TArray[] Initialize<TArray>(this TArray[] array, Func<int, TArray> Initializer)
        {
            Contract.Requires(array != null);
            Contract.Requires(Initializer != null);
            Contract.Ensures(Contract.Result<TArray[]>() != null);
            Contract.Ensures(Contract.Result<TArray[]>() == array);

            for(var i = 0; i < array.Length; i++)
                array[i] = Initializer(i);
            return array;
        }

        [Diagnostics.DebuggerStepThrough]
        public static TArray[] Initialize<TArray>(this TArray[] array, Func<TArray, int, TArray> Initializer)
        {
            Contract.Requires(array != null);
            Contract.Ensures(Contract.Result<TArray[]>() != null);

            for(var i = 0; i < array.Length; i++)
                array[i] = Initializer(array[i], i);
            return array;
        }

        [Diagnostics.DebuggerStepThrough]
        public static TArray[,] Initialize<TArray>(this TArray[,] array, Func<int, int, TArray> Initializer)
        {
            Contract.Requires(array != null);
            Contract.Ensures(Contract.Result<TArray[,]>() != null);

            var length_i = array.GetLength(0);
            var length_j = array.GetLength(1);
            for(var i = 0; i < length_i; i++)
                for(var j = 0; j < length_j; j++)
                    array[i, j] = Initializer(i, j);
            return array;
        }

        [Diagnostics.DebuggerStepThrough]
        public static TArray[][] Initialize<TArray>(this TArray[][] array, Func<int, TArray[]> ArrayInitializer, Func<int, int, TArray> Initializer)
        {
            Contract.Requires(array != null);
            Contract.Ensures(Contract.Result<TArray[][]>() != null);

            return array.Initialize(i => ArrayInitializer(i).Initialize(j => Initializer(i, j)));
        }

        [Diagnostics.DebuggerStepThrough]
        public static void Reverse<TArray>(this TArray[] array)
        {
            Contract.Requires(array != null);

            Array.Reverse(array);
        }

        [Diagnostics.DebuggerStepThrough]
        public static void SetValues<TArray>(this TArray[] array, int StartIndex, params TArray[] Values)
        {
            Contract.Requires(array != null);
            Contract.Requires(Values != null);
            Contract.Requires(Values.Length > 0);

            for(var i = 0; ((i + StartIndex) < array.Length) && (i < Values.Length); i++)
                array[i + StartIndex] = Values[i];
        }

        public static TArray[,] ToAligned<TArray>(this TArray[][] array)
        {
            Contract.Requires(array != null);
            Contract.Ensures(Contract.Result<TArray[,]>() != null);

            var l = array.Select(a => a.Length).ToArray();
            return new TArray[array.Length, l.Max()].Initialize((i, j) => j >= l[i] ? default(TArray) : array[i][j]);
        }

        [Diagnostics.DebuggerStepThrough]
        public static TArray[][] ToNonaligned<TArray>(this TArray[,] array)
        {
            Contract.Requires(array != null);
            Contract.Requires(array.GetLength(0) > 0);
            Contract.Requires(array.GetLength(1) > 0);
            Contract.Ensures(Contract.Result<TArray[][]>() != null);

            return new TArray[array.GetLength(0)][].Initialize(i => new TArray[array.GetLength(1)].Initialize(j => array[i, j]));
        }

        [Diagnostics.DebuggerStepThrough]
        public static void SetCol<TArray>(this TArray[,] array, TArray[] Col, int m = 0)
        {
            Contract.Requires(array != null);
            Contract.Requires(Col != null);

            var N = array.GetLength(0);
            var M = array.GetLength(1);

            if(N != Col.Length)
                throw new ArgumentException(string.Format("Длина столбца {0} не совпадает с высотой массива {1}",
                    Col.Length, N));
            if(m < 0 || m >= M)
                throw new ArgumentOutOfRangeException("m",
                    string.Format("Указанный номер столбца {0} выходит за пределы размеров массива {1}", m, M));
            for(var i = 0; i < N; i++)
                array[i, m] = Col[i];
        }

        [Diagnostics.DebuggerStepThrough]
        public static void SetRow<TArray>(this TArray[,] array, TArray[] Row, int n = 0)
        {
            Contract.Requires(array != null);
            Contract.Requires(Row != null);

            var N = array.GetLength(0);
            var M = array.GetLength(1);

            if(M != Row.Length)
                throw new ArgumentException(string.Format("Длина столбца {0} не совпадает с высотой массива {1}",
                    Row.Length, N));
            if(n < 0 || n >= N)
                throw new ArgumentOutOfRangeException("n",
                    string.Format("Указанный номер столбца {0} выходит за пределы размеров массива {1}", n, N));
            for(var i = 0; i < M; i++)
                array[n, i] = Row[i];
        }

        [Diagnostics.DebuggerStepThrough]
        public static TArray[] GetCol<TArray>(this TArray[,] array, int m)
        {
            Contract.Requires(array != null);
            Contract.Requires(array.GetLength(0) > 0);
            Contract.Requires(array.GetLength(1) > 0);
            Contract.Requires(m >= 0 && m < array.GetLength(1));
            Contract.Ensures(Contract.Result<TArray[]>() != null);

            var N = array.GetLength(0);
            var M = array.GetLength(1);

            if(m < 0 || m >= M)
                throw new ArgumentOutOfRangeException("m",
                    string.Format("Указанный номер столбца {0} выходит за пределы размеров массива {1}", m, M));

            var result = new TArray[N];
            for(var n = 0; n < N; n++)
                result[n] = array[n, m];
            return result;
        }

        [Diagnostics.DebuggerStepThrough]
        public static TArray[] GetRow<TArray>(this TArray[,] array, int n)
        {
            Contract.Requires(array != null);
            Contract.Requires(array.GetLength(0) > 0);
            Contract.Requires(array.GetLength(1) > 0);
            Contract.Requires(n >= 0 && n < array.GetLength(0));
            Contract.Ensures(Contract.Result<TArray[]>() != null);

            var N = array.GetLength(0);
            var M = array.GetLength(1);

            if(n < 0 || n >= N)
                throw new ArgumentOutOfRangeException("n",
                    string.Format("Указанный номер столбца {0} выходит за пределы размеров массива {1}", n, N));

            var result = new TArray[M];
            for(var m = 0; m < M; m++)
                result[m] = array[n, m];
            return result;
        }

        [Diagnostics.DebuggerStepThrough]
        public static void SwapRows<T>(this T[,] array, int i, int j)
        {
            Contract.Requires(array != null);
            Contract.Requires(array.GetLength(0) > 0);
            Contract.Requires(array.GetLength(1) > 0);
            Contract.Requires(i >= 0 && i < array.GetLength(1));
            Contract.Requires(j >= 0 && j < array.GetLength(1));

            var N = array.GetUpperBound(0);
            for(var ii = 0; ii <= N; ii++)
            {
                var temp = array[i, ii];
                array[i, ii] = array[j, ii];
                array[j, ii] = temp;
            }
        }

        /// <summary>Поменять местами два столбца двумерного массива</summary>
        /// <param name="array">Двумерный массив</param>
        /// <param name="i">Номер первого столбца</param>
        /// <param name="j">Номер второго столбца</param>
        /// <typeparam name="T">Тип элементов массива</typeparam>
        [Diagnostics.DebuggerStepThrough]
        public static void SwapCols<T>(this T[,] array, int i, int j)
        {
            Contract.Requires(array != null);
            Contract.Requires(array.GetLength(0) > 0);
            Contract.Requires(array.GetLength(1) > 0);
            Contract.Requires(i >= 0 && i < array.GetLength(0));
            Contract.Requires(j >= 0 && j < array.GetLength(0));

            var N = array.GetUpperBound(1);
            for(var ii = 0; ii <= N; ii++)
            {
                var temp = array[ii, i];
                array[ii, i] = array[ii, j];
                array[ii, j] = temp;
            }
        }

        //public static int GetMinIndex<T>(this T[] array, Comparison<T> compare)
        //{
        //    var index = 0;
        //    for(var i = 0; i < array.Length; i++)
        //    {
        //        if(compare(array[index], array[i]) < 0)  
        //    }
        //}

        /// <summary>Создать массив последовательных значений длины <paramref name="length"/> начиная с <paramref name="offset"/></summary>
        /// <param name="length">Длина массива</param>
        /// <param name="offset">НАчальное значение</param>
        /// <returns>Массив чисел длины <paramref name="length"/> начиная с <paramref name="offset"/></returns>
        public static int[] CreateSequence(int length, int offset = 0)
        {
            Contract.Requires(length > 0);
            Contract.Requires(offset >= 0);
            Contract.Requires(offset < length);
            Contract.Ensures(Contract.Result<int[]>() != null);
            Contract.Ensures(Contract.Result<int[]>().Length == length);

            var result = new int[length];
            for(var i = 0; i < length; i++)
                result[i] = i + offset;
            return result;
        }

        /// <summary>Создать копию массива с перемешанным содержимым</summary>
        /// <param name="array">Исходный массив</param>
        /// <typeparam name="T">Тип элементов массива</typeparam>
        /// <returns>Копия исходного массива с перемешанным созержимым</returns>
        public static T[] Mix<T>(this T[] array)
        {
            Contract.Requires(array != null);
            Contract.Ensures(Contract.Result<T[]>() != null);

            return ((T[])array.Clone()).MixRef();
        }

        /// <summary>Перемешать массив</summary>
        /// <typeparam name="T">Тип элементов массива</typeparam>
        /// <param name="array">Перемешиваемый массив</param>
        /// <returns>Исходный массив с перемешанным содержимым</returns>
        public static T[] MixRef<T>(this T[] array)
        {
            Contract.Requires(array != null);
            Contract.Requires(array.Length > 0);
            Contract.Ensures(Contract.Result<T[]>() != null);

            var length = array.Length - 1;
            var rnd = new Random();
            var temp = array[0];
            var index = 0;
            for(var i = 1; i <= length; i++)
                array[index] = array[index = rnd.Next(length)];
            array[index] = temp;

            return array;
        }

        /// <summary>Последовательно скопировать набор массивов в буффер</summary>
        /// <param name="A">Буфферный массив соответствующей длины</param>
        /// <param name="B">Перечень устанавливаемых значений</param>
        /// <typeparam name="T">Тип элементов массива</typeparam>
        public static void SetSubArrays<T>(this T[] A, params T[][] B)
        {
            Contract.Requires(A.Length == B.Select(b => b.Length).Sum());

            var index = 0;
            B.Foreach(b =>
            {
                b.CopyTo(A, index);
                index += b.Length;
            });
        }

        public static bool IsContains<T>(this T[] array, T item) { return Array.IndexOf(array, item) != -1; }
    }
}
